/*
moxl - Modular XML Library
Copyright (c) 2007, Malcolm Sparks

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package moxl.parser.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import moxl.parser.markup.types.CloseElementTagMarkup;
import moxl.parser.markup.types.OpenElementTagMarkup;
import moxl.raw.ElementLocation;
import moxl.raw.PointLocation;
import moxl.raw.RawAttribute;
import moxl.raw.RawCharacterData;
import moxl.raw.RawDocument;
import moxl.raw.RawElement;
import moxl.raw.RawNode;
import moxl.tokenizer.Token;

public class ParsedElement implements RawElement
{
	private OpenElementTagMarkup openMarkup;
	private List<RawNode> children;
	private ParsedAttributeMap attributeMap;

	transient List<RawElement> childElements;
	private final String namespace;
	private final ElementLocation location;
	private final ParsedDocument containingDocument;

	private static PointLocation createPointLocation(Token token)
	{
		return new PointLocation(token.getLocation().getLine(), token.getLocation().getColumn());
	}

	public ParsedElement(ParsedDocument containingDocument, OpenElementTagMarkup openMarkup, List<RawNode> children, CloseElementTagMarkup closeMarkup,
	        String namespace)
	{
		this.containingDocument = containingDocument;
		if (containingDocument == null)
		{
			throw new IllegalArgumentException("containingDocument cannot be null");
		}
		if (closeMarkup != null)
		{
			location = new ElementLocation(containingDocument.getResource(), createPointLocation(openMarkup.getFirstToken()), createPointLocation(closeMarkup
			        .getFirstToken()));
		}
		else
		{
			location = new ElementLocation(containingDocument.getResource(), createPointLocation(openMarkup.getFirstToken()), createPointLocation(openMarkup
			        .getFirstToken()));
		}

		if (children == null)
		{
			children = new ArrayList<RawNode>();
		}
		this.openMarkup = openMarkup;
		this.children = children;

		if (namespace == null)
		{
			throw new IllegalArgumentException("namespace cannot be null");
		}
		this.namespace = namespace;
	}

	public String getTag()
	{
		return openMarkup.getTagName();
	}

	public RawNode getChildNode(int i)
	{
		return children.get(i);
	}

	public Collection<? extends RawAttribute> getAttributes()
	{
		return getAttributeMap().getAttributes();
	}

	public RawAttribute getAttribute(String name)
	{
		if (getAttributeMap().containsAttribute(name))
		{
			return getAttributeMap().getAttribute(name);
		}
		return null;
	}

	private ParsedAttributeMap getAttributeMap()
	{
		if (attributeMap == null)
		{
			attributeMap = new ParsedAttributeMap(openMarkup);
		}
		return attributeMap;
	}

	public List<RawElement> getChildElements()
	{
		if (childElements == null)
		{
			List<RawElement> list = new ArrayList<RawElement>();
			for (int i = 0; i < children.size(); i++)
			{
				RawNode node = children.get(i);
				if (node instanceof RawElement)
				{
					list.add((RawElement) node);
				}
			}
			childElements = Collections.unmodifiableList(list);
		}
		return childElements;
	}

	public List<RawElement> getChildElements(String tagName)
	{
		List<RawElement> list = new ArrayList<RawElement>();
		for (int i = 0; i < children.size(); i++)
		{
			RawNode xmlNode = children.get(i);
			if (xmlNode instanceof RawElement)
			{
				RawElement element = (RawElement) xmlNode;
				if ((element).getTag().equals(tagName))
				{
					list.add(element);
				}
			}
		}
		return Collections.unmodifiableList(list);
	}

	public RawElement getChildElement(String tagName, int pos)
	{
		if (pos < 1)
		{
			throw new IllegalArgumentException("This method's pos parameter is 1-based, like XPath.");
		}
		List<RawElement> list = getChildElements(tagName);
		if (pos > list.size())
		{
			return null;
		}
		else
		{
			return (RawElement) list.get(pos - 1);
		}
	}

	public RawElement getRequiredChildElement(String name) throws InvalidXmlException
	{
		List<RawElement> children = getChildElements(name);
		if (children.size() == 0)
		{
			throw new InvalidXmlException("Required child element not found: " + name);
		}
		else if (children.size() > 1)
		{
			throw new InvalidXmlException("More than one child element found with name " + name);
		}
		else
		{
			return children.get(0);
		}
	}

	public String getContent()
	{
		StringBuffer content = new StringBuffer();
		for (int i = 0; i < children.size(); i++)
		{
			RawNode xmlNode = children.get(i);
			if (xmlNode instanceof ParsedCharacterData)
			{
				content.append(((RawCharacterData) xmlNode).getText());
			}
		}
		return content.toString();
	}

	public String getNodeName()
	{
		return "element";
	}

	public String getNamespace()
	{
		return namespace;
	}

	public List<RawNode> getChildNodes()
	{
		return Collections.unmodifiableList(children);
	}

	@Override
    public String toString()
	{
		StringBuilder sb = new StringBuilder("[");
		sb.append(getTag());
		sb.append("@");
		sb.append(getLocation());
		sb.append("]");
		return sb.toString();
	}

	public boolean isEmpty()
	{
		throw new UnsupportedOperationException("TODO");
	}

	public boolean containsCharacterData()
	{
		throw new UnsupportedOperationException("TODO");
	}

	public ElementLocation getLocation()
	{
		return location;
	}

	public boolean hasLocation()
	{
		return true;
	}

	public int countNodes()
	{
		return children.size();
	}

	public int countElements()
	{
		if (childElements != null)
		{
			return childElements.size();
		}
		int count = 0;
		for (RawNode node : children)
		{
			if (node instanceof RawElement)
			{
				count++;
			}
		}
		return count;
	}

	public RawDocument getOwningDocument()
    {
	    return containingDocument;
    }

	public boolean containsCharacterDataExcludingWhitespace()
    {
	    throw new UnsupportedOperationException("TODO");
    }
}
